package com.onlyxiahui.common.action.description.util.reflect;

import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;

/**
 * Description <br>
 * Date 2020-04-06 14:05:31<br>
 * 
 * @author XiaHui [onlovexiahui@qq.com]<br>
 * @since 1.0.0
 */

public class OnlyFieldTypeUtil {

	public static Type getFieldType(final Class<?> clazz, final Type type, Type fieldType) {
		if (clazz == null || type == null) {
			return fieldType;
		}

		if (fieldType instanceof GenericArrayType) {
			GenericArrayType genericArrayType = (GenericArrayType) fieldType;
			Type componentType = genericArrayType.getGenericComponentType();
			Type componentTypeX = getFieldType(clazz, type, componentType);
			if (componentType != componentTypeX) {
				Type fieldTypeX = Array.newInstance(OnlyTypeUtils.getClass(componentTypeX), 0).getClass();
				return fieldTypeX;
			}

			return fieldType;
		}

		if (!OnlyTypeUtils.isGenericParamType(type)) {
			return fieldType;
		}

		if (fieldType instanceof TypeVariable) {
			ParameterizedType paramType = (ParameterizedType) OnlyTypeUtils.getGenericParamType(type);
			Class<?> parameterizedClass = OnlyTypeUtils.getClass(paramType);
			final TypeVariable<?> typeVar = (TypeVariable<?>) fieldType;

			TypeVariable<?>[] typeVariables = parameterizedClass.getTypeParameters();
			for (int i = 0; i < typeVariables.length; ++i) {
				if (typeVariables[i].getName().equals(typeVar.getName())) {
					fieldType = paramType.getActualTypeArguments()[i];
					return fieldType;
				}
			}
		}

		if (fieldType instanceof ParameterizedType) {
			ParameterizedType parameterizedFieldType = (ParameterizedType) fieldType;

			Type[] arguments = parameterizedFieldType.getActualTypeArguments();
			TypeVariable<?>[] typeVariables;
			ParameterizedType paramType;
			if (type instanceof ParameterizedType) {
				paramType = (ParameterizedType) type;
				typeVariables = clazz.getTypeParameters();
			} else if (clazz.getGenericSuperclass() instanceof ParameterizedType) {
				paramType = (ParameterizedType) clazz.getGenericSuperclass();
				typeVariables = clazz.getSuperclass().getTypeParameters();
			} else {
				paramType = parameterizedFieldType;
				typeVariables = type.getClass().getTypeParameters();
			}

			boolean changed = getArgument(arguments, typeVariables, paramType.getActualTypeArguments());
			if (changed) {
				fieldType = new ParameterizedTypeImpl(arguments, parameterizedFieldType.getOwnerType(),
						parameterizedFieldType.getRawType());
				return fieldType;
			}
		}

		return fieldType;
	}

	@SuppressWarnings("rawtypes")
	private static boolean getArgument(Type[] typeArgs, TypeVariable[] typeVariables, Type[] arguments) {
		if (arguments == null || typeVariables.length == 0) {
			return false;
		}

		boolean changed = false;
		for (int i = 0; i < typeArgs.length; ++i) {
			Type typeArg = typeArgs[i];
			if (typeArg instanceof ParameterizedType) {
				ParameterizedType p_typeArg = (ParameterizedType) typeArg;
				Type[] p_typeArg_args = p_typeArg.getActualTypeArguments();
				boolean p_changed = getArgument(p_typeArg_args, typeVariables, arguments);
				if (p_changed) {
					typeArgs[i] = new ParameterizedTypeImpl(p_typeArg_args, p_typeArg.getOwnerType(), p_typeArg.getRawType());
					changed = true;
				}
			} else if (typeArg instanceof TypeVariable) {
				for (int j = 0; j < typeVariables.length; ++j) {
					if (typeArg.equals(typeVariables[j])) {
						typeArgs[i] = arguments[j];
						changed = true;
					}
				}
			}
		}

		return changed;
	}

	public static Type getInheritGenericType(Class<?> clazz, Type type, TypeVariable<?> tv) {
		GenericDeclaration gd = tv.getGenericDeclaration();

		Class<?> class_gd = null;
		if (gd instanceof Class) {
			class_gd = (Class<?>) tv.getGenericDeclaration();
		}

		Type[] arguments = null;
		if (class_gd == clazz) {
			if (type instanceof ParameterizedType) {
				ParameterizedType ptype = (ParameterizedType) type;
				arguments = ptype.getActualTypeArguments();
			}
		} else {
			for (Class<?> c = clazz; c != null && c != Object.class && c != class_gd; c = c.getSuperclass()) {
				Type superType = c.getGenericSuperclass();

				if (superType instanceof ParameterizedType) {
					ParameterizedType p_superType = (ParameterizedType) superType;
					Type[] p_superType_args = p_superType.getActualTypeArguments();
					getArgument(p_superType_args, c.getTypeParameters(), arguments);
					arguments = p_superType_args;
				}
			}
		}

		if (arguments == null || class_gd == null) {
			return null;
		}

		Type actualType = null;
		TypeVariable<?>[] typeVariables = class_gd.getTypeParameters();
		for (int j = 0; j < typeVariables.length; ++j) {
			if (tv.equals(typeVariables[j])) {
				actualType = arguments[j];
				break;
			}
		}

		return actualType;
	}
}
